package tetris.simulator;

import java.util.*;

public class State {
	public static final int COLS = 10;
	public static final int ROWS = 21;
	public static final int N_PIECES = 7;

	public boolean lost;
	
	//current turn
	private int turn;
	private int cleared;
	
	//each square in the grid - 0 means empty - other values mean the type of piece it was placed
	private int[][] field;
	//top row+1 of each column
	//0 means empty
	private int[] top;
	
	//number of next piece
	protected int nextPiece;
	
	//all legal moves - first index is piece type - then a list of 2-length arrays
	public static int[][][] legalMoves = new int[N_PIECES][][];
	
	//indices for legalMoves
	public static final int ORIENT = 0;
	public static final int SLOT = 1;
	
	//possible orientations for a given piece type
	public static int[] pOrients = {1,2,4,4,4,2,2};
	
	//the next several arrays define the piece vocabulary in detail
	//width of the pieces [piece ID][orientation]
	public static int[][] pWidth = {
			{2},
			{1,4},
			{2,3,2,3},
			{2,3,2,3},
			{2,3,2,3},
			{3,2},
			{3,2}
	};
	//height of the pieces [piece ID][orientation]
	public static int[][] pHeight = {
			{2},
			{4,1},
			{3,2,3,2},
			{3,2,3,2},
			{3,2,3,2},
			{2,3},
			{2,3}
	};
	public static int[][][] pBottom = {
		{{0,0}},
		{{0},{0,0,0,0}},
		{{0,0},{0,1,1},{2,0},{0,0,0}},
		{{0,0},{0,0,0},{0,2},{1,1,0}},
		{{0,1},{1,0,1},{1,0},{0,0,0}},
		{{0,0,1},{1,0}},
		{{1,0,0},{0,1}}
	};
	public static int[][][] pTop = {
		{{2,2}},
		{{4},{1,1,1,1}},
		{{3,1},{2,2,2},{3,3},{1,1,2}},
		{{1,3},{2,1,1},{3,3},{2,2,2}},
		{{3,2},{2,2,2},{2,3},{1,2,1}},
		{{1,2,2},{3,2}},
		{{2,2,1},{2,3}}
	};
	
	//initialize legalMoves
	{
		//for each piece type
		for(int i = 0; i < N_PIECES; i++) {
			//figure number of legal moves
			int n = 0;
			for(int j = 0; j < pOrients[i]; j++) {
				//number of locations in this orientation
				n += COLS+1-pWidth[i][j];
			}
			//allocate space
			legalMoves[i] = new int[n][2];
			//for each orientation
			n = 0;
			for(int j = 0; j < pOrients[i]; j++) {
				//for each slot
				for(int k = 0; k < COLS+1-pWidth[i][j];k++) {
					legalMoves[i][n][ORIENT] = j;
					legalMoves[i][n][SLOT] = k;
					n++;
				}
			}
		}
	
	}
	
	public void printState() {
		for(int i=ROWS-1;i>=0;i--)
		{
			for(int j=0;j<COLS;j++)
			{
				System.out.print(field[i][j]);
				System.out.print(" ");
			}
			System.out.println();
		}
	}
	
	public int findLines(int maxtop){
		int counts =0;
		for(int i=0;i<maxtop;i++)
		{
			for(int j=0;j<COLS;j++)
			{
				if(field[i][j] == 0)
					break;
				if(j == (COLS-1))
					counts++;
			}
		}
		return counts;
	}
	
	public int [] findHolesNBlockades(){
		int holes =0;
		int blockades = 0;
		boolean foundHole = false;
		int [] top = getTop();
		for(int k=0;k<top.length;k++)//cols
		{
			for(int i=0;i<top[k];i++)//rows
			{
				if(field[i][k] == 0){
					holes++;
					foundHole = true;
				}
				else if(foundHole)
					blockades++;
			}			
		}
		
		int [] ret ={holes , blockades};
		return ret;
	}
	
	public int [] findFlatnWell(){
		int [] top_ = getTop();
//		Number [] top = new Number[top_.length];
//		for(int i=0;i<top.length;i++)top[i]=top_[i];
//		double var = Statistics.getStdDev(top);
		int well =0;
		int flatness = 0;
		for(int i=0;i<top_.length;i++)
		{
			int j = (i-1)<0? 0 : i-1;
			int k = (i+1)==top_.length ? i : i+1;
			if((top_[i]<(top_[j]-1)) && (top_[i]<(top_[k]-1)))
				well += 1.0;
			flatness +=Math.abs(top_[i]-top_[k]); 
			
		}
		int [] ret = {flatness,well};;
		return ret;
	}
	
	public int [] findAdjacency(State newS){
		int [] adjacency = {0, 0, 0};//[0]=boundaryWall [1]=blocks [2]=free
		int [][]newField = newS.getField();
		List<Integer> row_L = new ArrayList<Integer>();
		List<Integer> col_L = new ArrayList<Integer>();
		for(int i=0;i<ROWS;i++)
		{
			for(int j=0;j<COLS;j++)
			{
				if(field[i][j] != newField[i][j])
				{
					row_L.add(i);
					col_L.add(j);
				}
			}
		}
//		 System.out.println(" ArrayList Elements");
//	      System.out.print("\t" + row_L +" , " +col_L);
		for(int i=0;i<row_L.size();i++)
		{
			int [] r = {-1,0,1};
			int [] c = {-1,0,1};
			for(int j=0;j<r.length;j++)
				for(int k=0;k<c.length;k++)
				{
					int ro = row_L.get(i) + r[j];
					int co = col_L.get(i) + c[k];
					if(ro<0 || ro == ROWS || co<0 || co==COLS)
					{
//						System.out.println("ro: "+ro+" , co:"+co);
						adjacency[0]++ ;
					}
					ro = ro<0 ? 0 : ro;
					ro = ro==ROWS ? ROWS-1 : ro;
					co = co<0 ? 0 : co;
					co = co==COLS ? (COLS-1) : co;
//					System.out.println("ro: "+ro+" , co:"+co);
					if(newField[ro][co] == 0)
						adjacency[2]++;
					else if(newField[row_L.get(i)][col_L.get(i)] != newField[ro][co])
					{
//						System.out.println("ro: "+ro+" , co:"+co);
						adjacency[1]++;
					}					
				} 
		}		
		return adjacency;
	}
	
	public int [] findAdjacency2(State newS){
		int [] adjacency = {0, 0, 0};//[0]=boundaryWall [1]=blocks [2]=free
		int [][]newField = newS.getField();
		List<Integer> row_L = new ArrayList<Integer>();
		List<Integer> col_L = new ArrayList<Integer>();
		for(int i=0;i<ROWS;i++)
		{
			for(int j=0;j<COLS;j++)
			{
				if(field[i][j] != newField[i][j])
				{
					row_L.add(i);
					col_L.add(j);
				}
			}
		}
//		 System.out.println(" ArrayList Elements");
//	      System.out.print("\t" + row_L +" , " +col_L);
		for(int i=0;i<row_L.size();i++)
		{
			int row = row_L.get(i);
			int col = col_L.get(i);
			//check on bottom side of current block
			int r;
			if((row-1) < 0)
			{
				adjacency[0]++;
				r = 0;
			}
			else
				r = row -1;
			if (newField[r][col] == 0)
				adjacency[2]++;
			if (newField[r][col] != newField[row][col])
				adjacency[1]++;
			
			//check on left side of current block
			int c;
			if((col-1) < 0)
			{
				adjacency[0]++;
				c = 0;
			}
			else
				c = col-1;
			if (newField[row][c] == 0)
				adjacency[2]++;
			if (newField[row][c] != newField[row][col])
				adjacency[1]++;
			
			//check on right side of current block
			if((col+1) == COLS)
			{
				adjacency[0]++;
				c = COLS-1;
			}
			else
				c = col+1;
			if (newField[row][c] == 0)
				adjacency[2]++;
			if (newField[row][c] != newField[row][col])
				adjacency[1]++;			
		} 				
		return adjacency;
	}
	
	public int[] getTop() {
		return top;
	}
	
	public int[][] getField() {
		return field;
	}
	
	public int getNextPiece() {
		return nextPiece;
	}
	
	public boolean hasLost() {
		return lost;
	}
	
	public int getRowsCleared() {
		return cleared;
	}
	
	public int getTurnNumber() {
		return turn;
	}
	
	//constructor
	public State() {
		lost = false;
		turn = 0;
		cleared = 0;
		
		field = new int[ROWS][COLS];
		top = new int[COLS];

		nextPiece = randomPiece();
	}

	//copy constructor
	public State(State other) {
		lost = other.lost;
		turn = other.turn;
		cleared = other.cleared;

		field = new int[ROWS][];
		for (int r = 0; r < ROWS; r++)
			field[r] = other.field[r].clone();

		top = other.top.clone();

		nextPiece = other.nextPiece;
	}
	
	//random integer, returns 0-6
	private int randomPiece() {
		return (int)(Math.random()*N_PIECES);
	}
	
	//gives legal moves for next piece
	public int[][] legalMoves() {
		return legalMoves[nextPiece];
	}
	
	//make a move based on the move index - its order in the legalMoves list
	public void makeMove(int move) {
		makeMove(legalMoves[nextPiece][move]);
	}
	
	//make a move based on an array of orient and slot
	public void makeMove(int[] move) {
		makeMove(move[ORIENT],move[SLOT]);
	}
	
	//returns false if you lose - true otherwise
	public boolean makeMove(int orient, int slot) {
		turn++;
		//height if the first column makes contact
		int height = top[slot]-pBottom[nextPiece][orient][0];
		//for each column beyond the first in the piece
		for(int c = 1; c < pWidth[nextPiece][orient];c++) {
			height = Math.max(height,top[slot+c]-pBottom[nextPiece][orient][c]);
		}
		
		//check if game ended
		if(height+pHeight[nextPiece][orient] >= ROWS) {
			lost = true;
			return false;
		}

		
		//for each column in the piece - fill in the appropriate blocks
		for(int i = 0; i < pWidth[nextPiece][orient]; i++) {
			
			//from bottom to top of brick
			for(int h = height+pBottom[nextPiece][orient][i]; h < height+pTop[nextPiece][orient][i]; h++) {
				field[h][i+slot] = nextPiece + 1;//turn;
			}
		}
		
		//adjust top
		for(int c = 0; c < pWidth[nextPiece][orient]; c++) {
			top[slot+c]=height+pTop[nextPiece][orient][c];
		}
		
		int rowsCleared = 0;
		
		//check for full rows - starting at the top
		for(int r = height+pHeight[nextPiece][orient]-1; r >= height; r--) {
			//check all columns in the row
			boolean full = true;
			for(int c = 0; c < COLS; c++) {
				if(field[r][c] == 0) {
					full = false;
					break;
				}
			}
			//if the row was full - remove it and slide above stuff down
			if(full) {
				rowsCleared++;
				cleared++;
				//for each column
				for(int c = 0; c < COLS; c++) {

					//slide down all bricks
					for(int i = r; i < top[c]; i++) {
						field[i][c] = field[i+1][c];
					}
					//lower the top
					top[c]--;
					while(top[c]>=1 && field[top[c]-1][c]==0)	top[c]--;
				}
			}
		}
	
		//pick a new piece
		nextPiece = randomPiece();
		
		return true;
	}
	
	public String toString() {
		String s = "";
		//		System.out.print(field);

		s += "turn: " + turn + "  cleared: " + cleared +
			"  next: " + nextPiece + "  lost: " + lost + "\n";
		s += "field:\n";
		for (int r = ROWS-1; r >= 0; r--) {
			s += Arrays.toString(field[r]) + "\n";
		}

		s += "\ntop:\n" + Arrays.toString(top) + "\n";
		return s;
	}
}


